
/*
 * En este archivo implementamos todas las funciones definidas en
 * el archivo entradasalida.h
 *
 * Estas mismas funciones deberían usarse en la versión paralela.
 */

#include "entradasalida.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>


/* Definimos algunas constantes */

#define MAX_LONG_ARCHIVO 255
#define MAX_LONG_FECHA 40
#define MAX_LONG_VALOR 500


/* Prototipado de funciones locales */

struct MATRIZ* obtenerMatrizAleatoria();
struct MATRIZ* obtenerMatrizPorArchivo();
struct MATRIZ* obtenerMatrizPorTeclado();
int verificarMatriz(struct MATRIZ *matriz);
int leerValorInt();
double leerValorDouble();
char leerValorChar();
void leerValorText(char *valueBuffer);
void limpiarTexto(char *texto);
int archivoExistente(char *nombreArchivo);
void guardarContenidoMatriz(char *nombreArchivo, struct MATRIZ *matriz);
void generarTimestamp(char *timestamp);


/*
 * Implementación de la función definida en cabecera.
 */
struct MATRIZ* obtenerMatrizDelUsuario() {

    // Aqui referenciaremos la matriz obtenida
    struct MATRIZ *matriz;

    // Variable para determinar si debemos volver a pedir los datos al usuario.
    int pedirDatos = 1;
    int validarMatriz; // Indica si debemos validar la matriz obtenida
    int guardarMatriz; // Indica si debemos guardar la matriz obtenida

    while (pedirDatos == 1) {

        // Mostramos los mensajes correspondientes
        printf("Para el ingreso de datos desea:\n");
        printf("1) Ingresar los datos por teclado\n");
        printf("2) Obtener los datos por archivo\n");
        printf("3) Generar los datos aleatoriamente\n");
        printf("Seleccione una opción: ");

        // Obtenemos lo que ingresó el usuario
        char opcionSeleccionada = leerValorChar();

        // En base a lo que ingresó el usuario, decidimos que hacer.
        switch(opcionSeleccionada) {

            case '1':

                // Hay que pedirle al usuario que ingrese la matriz por teclado
                matriz = obtenerMatrizPorTeclado();

                // Hay qe validar la matriz ingresada y guardarla
                validarMatriz = 1;
                guardarMatriz = 1;
                
                break;

            case '2':

                // Hay que leer la matriz desde un archivo
                matriz = obtenerMatrizPorArchivo();

                // Hay que validar la matriz obtenida, pero no guardarla
                validarMatriz = 1;
                guardarMatriz = 0;

                break;

            case '3':

                // Hay que generar una matriz aleatoria
                matriz = obtenerMatrizAleatoria();

                // Hay que validar y guardar
                validarMatriz = 1;
                guardarMatriz = 1;

                break;

            default:

                // No seleccionó nada válido, no validamos ni guardamos.
                printf("No seleccionó una opción válida ...\n\n");
                validarMatriz = 0;
                guardarMatriz = 0;

                break;
                
        }

        // Validar matriz si es necesario
        if(validarMatriz) {

            // Si la matriz es válida, permitimos la salida del bucle
            if(verificarMatriz(matriz)) {
                pedirDatos = 0;
            } else {
                pedirDatos = 1; // No permitimos la salida del bucle
            }

        }

    }

    // Si es necesario guardar la matriz, la guardamos
    if(guardarMatriz) {
        almacenarMatriz(matriz, "matriz ingresada");
    }

    // Devolvemos la matriz generada
    return matriz;

}

/*
 * Implementación de la función definida en cabecera.
 */
void almacenarMatriz(struct MATRIZ *matriz, char *nombreMatriz){

    // Preguntamos al usuario si desea guardar la matriz
    printf("¿Desea almacenar la %s? (s/n): ", nombreMatriz);
    
    char respuesta; // Acá almacenaremos la respuesta del usuario
    int pedirRespuesta; // Variable usada para volver a pedir una respuesta

    do {

        // Pedimos una respuesta válida
        respuesta = leerValorChar();

        // Segun lo que respondió el usuario tomamos una acción
        switch (respuesta) {

            // Si desea guardar la matriz
            case 's': {

                int pedirNombre;// Variable de condicion de salida del bucle

                do {

                    // Obtener el nombre del archivo
                    char nombreArchivo[MAX_LONG_ARCHIVO];
                    printf("Especifique un nombre para el archivo: ");
                    leerValorText(nombreArchivo);

                    // Verificar que el archivo no exista
                    if(archivoExistente(nombreArchivo)) {

                        printf("Error. Ya existe un archivo con el nombre indicado.\n");
                        pedirNombre = 1;

                    } else {

                        // Guardamos efectivamente la matriz
                        guardarContenidoMatriz(nombreArchivo, matriz);
                        pedirNombre = 0; // Salimos del bucle

                    }

                } while (pedirNombre);

                printf("Matriz guardada exitósamente.\n");
                pedirRespuesta = 0;// Permitimos la salida del bucle
                break;

            }

            // Si no desea guardar la matriz
            case 'n': {

                printf("No guardamos la matriz.\n");
                pedirRespuesta = 0; // Permitimos la salida del bucle
                break;

            }

            // Cualquier otra respuesta
            default: {

                // Imprimimos un mensaje de error y indicamos que se pida otra respuesta
                printf("Respuesta inválida, por favor responda 's' o 'n': ");
                pedirRespuesta = 1;
                break;

            }

        }

    } while (pedirRespuesta);

}

/*
 * Implementación de la función definida en entradasalida.h
 */
void imprimirEnOctave(struct MATRIZ *matriz, char *nombre) {

    printf("Generando entrada de Octave para la matriz %s:\n", nombre);
    printf("%s=[", nombre);

    int i,j;

    for (i=0; i<matriz->dimension; i++) {
        printf("[");
        for (j=0; j<matriz->dimension; j++) {
            printf("%f", matriz->elementos[i][j]);
            if (j < matriz->dimension - 1) {
                printf(",");
            }
        }
        printf("]");
        if (i < matriz->dimension - 1) {
            printf(";");
        }
    }

    printf("]");

}

/*
 * Le solicita al usuario que ingrese la matriz por teclado.
 * Primero solicita la dimensión de la matriz.
 * Luego solicita cada elemento de la matriz.
 */
struct MATRIZ *obtenerMatrizPorTeclado() {

    struct MATRIZ *matriz; // Aqui guardamos la matriz obtenida.
    int dimension = 0; // Aqui guardaremos la dimensiond de la matriz

    // Vamos a obtener la dimensión de la matriz
    int pedirDimension = 1;

    do {

        printf("\nIngrese la dimensión de la matriz: ");
        dimension = leerValorInt();

        // Chequeamos que la dimensión sea mayor o igual que 3.
        if (dimension >= 3) {

            matriz = crearMatriz(dimension); // Reservamos memoria para la matriz
            double **elementos = matriz->elementos; // Buffereamos los elementos de la matriz

            // Ahora pedimos los elementos uno a uno.
            int i,j;
            for (i = 0; i < dimension; i++) {
                for (j = 0; j < dimension; j++) {

                    // Solicitamos el elemento actual
                    printf("Ingrese el valor %d,%d : ",i+1,j+1);
                    elementos[i][j] = leerValorDouble();

                }
            }

            // Permitimos la salida del bucle.
            pedirDimension = 0;

        } else { // Dimensión incorrecta
            printf("Error: La dimensión de la matriz debe ser mayor que 2.");
        }

    } while(pedirDimension);

    // Devolvemos la matriz obtenida
    return matriz;

}


/*
 * Lee una matriz cuadrada desde un archivo.
 * Primero solicita el nombre del archivo y luego procede a leer la matriz
 * desde ese archivo.
 */
struct MATRIZ *obtenerMatrizPorArchivo() {

    FILE *filePointer; // Referencia al archivo a leer
    int pedirOtroArchivo = 1; // Variable de condicion del bucle
    struct MATRIZ *matriz; // Aqui guardaremos la matriz a devolver

    // Primero obtenemos el nombre del archivo e intentamos abrirlo
    do {

        char nombreArchivo[MAX_LONG_ARCHIVO]; // Aqui guardamos el nombre del archivo
        printf("Ingrese el nombre del archivo: ");
        leerValorText(nombreArchivo);

        // Abrimos el archivo especificado
        filePointer = fopen(nombreArchivo,"r");

        // Si el archivo se pudo abrir continuamos, sino salimos del bucle
        if (filePointer != NULL) {

            // Trataremos de leer la dimension
            int dimension;
            char resultadoLectura; // Resultado de la llamada a fscanf
            resultadoLectura = fscanf(filePointer, "dimension = %d\n", &dimension);

            if(resultadoLectura == EOF) { // Si el archivo esta vacío
                printf("El archivo está vacío.\n");
            } else if(resultadoLectura != 1) { // Si no se leyó la dimension
                printf("No se ha especificado una dimensión en el archivo.\n");
            } else if (dimension < 3) { // Si la dimensión leída es inválida
                printf("El archivo contiene una matriz de dimension menor a 3x3.\n");
            } else {

                // Creamos la matriz a leer
                matriz = crearMatriz(dimension);

                // Tratamos de leer todos los valores
                int contadorValores = 0;
                char dummyChar; // Usado para leer valores basura

                while(!feof(filePointer)) {

                    // Indica el valor, fila y columna del elemento que leemos en esta iteración
                    long double valorActual;
                    int fila, columna;
                    resultadoLectura = fscanf(
                            filePointer, "matriz[%d][%d] = %Lf\n", &fila, &columna, &valorActual);

                    // Solo asignamos los valores leidos si se leyeron las 3 variables
                    if(resultadoLectura == 3) {
                        matriz->elementos[fila][columna] = valorActual; // Asignamos el elemento leído
                        contadorValores++;
                    } else {
                        fread(&dummyChar, sizeof(char), 1, filePointer);
                    }

                }

                // Cerramos el archivo
                fclose(filePointer);

                // Determinamos si se leyeron todos los valores necesarios
                if(contadorValores == dimension * dimension) {
                    pedirOtroArchivo = 0;
                    printf("Matriz leída exitósamente\n");
                } else {
                    printf("El archivo indicado no posee una matriz válida\n");
                }
                
            }

        } else {
            printf("El archivo no se pudo abrir o no existe.\n");
        }

    } while (pedirOtroArchivo);

    // Devolvemos la matriz obtenida
    return matriz;
    
}


/*
 * Genera una matriz cuadrada con valores aleatorios.
 * La dimensión de la matriz es solicitada al usuario.
 * La matriz generada es diagonal dominante.
 */
struct MATRIZ *obtenerMatrizAleatoria() {

    struct MATRIZ *matriz; // Aqui guardamos la matriz obtenida.
    int dimension = 0; // Aqui guadaremos la dimension de la matriz

    // Vamos a obtener la dimensión de la matriz
    int pedirDimension = 1;

    do {

        printf("\nIngrese la dimensión de la matriz: ");
        dimension = leerValorInt();

        // Chequeamos que la dimensión sea mayor o igual que 3.
        if (dimension >= 3) {

            matriz = crearMatriz(dimension); // Reservamos memoria para la matriz

             // Asignamos valores aleatorios a los elementos de la matriz
            int i,j; // Variables de iteracion
            double suma; // Aca guardamos la suma de todos los elementos de la fila
            
            for(i=0; i<dimension; i++) {

                suma = 0; // Reiniciamos la suma de la fila

                for(j=0; j<dimension; j++) {

                    // Solo asignamos los elementos que no son de la diagonal
                    if(i!=j) {
                        int valor = rand() % 10;
                        matriz->elementos[i][j] = valor;
                        suma += abs(valor);
                    }

                }

                // Nos aseguramos que sea una matriz diagonal dominante
                matriz->elementos[i][i] = -2 * suma;
                
            }

            // Permitimos la salida del bucle.
            pedirDimension = 0;

        } else { // Dimensión incorrecta
            printf("Error: La dimensión de la matriz debe ser mayor que 2.");
        }

    } while(pedirDimension);

    // Devolvemos la matriz obtenida
    return matriz;

}


/*
 * Esta función obtiene una cadena desde el teclado.
 */
void leerValorText(char *valueBuffer) {

    int pedirValor; // Usaremos esta variable para saber si debemos volver a pedir el valor

    do { // Iteremos para pedir el valor

        // Leemos una cadena completa del teclado y la limpiamos
        char *buffer = fgets(valueBuffer, MAX_LONG_VALOR, stdin);
        limpiarTexto(buffer);

        // Ahora chequeamos lo que se ingresó
        if(buffer == NULL) {

            pedirValor = 1; // Si la llamada retornó null, no se ingresó nada válido

        } else if (strlen(buffer)==0) {

            printf("\tCadena vacía, ingrese nuevamente: ");
            pedirValor = 1;

        } else {
            pedirValor = 0;
        }

    } while (pedirValor);

}


/*
 * Esta función obtiene un caracter desde el teclado.
 * La función no retorna hasta obtener un caracter válido.
 */
char leerValorChar() {

    int pedirValor; // Usaremos esta variable para saber si debemos volver a pedir el valor
    char valueBuffer[MAX_LONG_VALOR];// Buffer del valor ingresado por teclado
    char valorObtenido; // Aqui guardaremos el valor a devolver

    do { // Iteremos para pedir el valor

        // Leemos una cadena completa del teclado y la limpiamos
        char *buffer = fgets(valueBuffer, MAX_LONG_VALOR, stdin);
        limpiarTexto(buffer);

        // Ahora chequeamos lo que se ingresó
        if(buffer == NULL) {

            pedirValor = 1; // Si la llamada retornó null, no se ingresó nada válido

        } else if (strlen(buffer)!=1) {

            printf("\tValor incorrecto, ingrese nuevamente: ");
            pedirValor = 1;

        } else {
            valorObtenido = buffer[0];
            pedirValor = 0;
        }

    } while (pedirValor);

    // Retornamos el valor obtenido
    return valorObtenido;

}


/*
 * Esta función obtiene un número entero desde el teclado.
 * La función no retorna hasta obtener un número válido.
 */
int leerValorInt() {

    int pedirValor; // Usaremos esta variable para saber si debemos volver a pedir el valor
    char valueBuffer[MAX_LONG_VALOR];// Buffer del valor ingresado por teclado
    int valorObtenido; // Aqui guardaremos el valor a devolver

    do { // Iteremos para pedir el valor

        // Leemos una cadena completa del teclado y la limpiamos
        fflush(stdin);
        char *buffer = fgets(valueBuffer, MAX_LONG_VALOR, stdin);
        limpiarTexto(buffer);

        // Ahora chequeamos lo que se ingresó
        if(buffer == NULL) {

            pedirValor = 1; // Si la llamada retornó null, no se ingresó nada válido

        } else if (!strcmp(buffer, "0")) {

            valorObtenido = 0;  // El valor ingresado es 0, devolvemos el número 0.
            pedirValor = 0;     // Ya tenemos el valor, no seguir iterando

        } else {

            // Ahora debemos convertir la cadena ingresada a un número.
            valorObtenido = atoi(buffer);

            // Si devuelve 0, el valor obtenido es erróneo.
            if(valorObtenido == 0) {
                printf("\tValor incorrecto, ingrese nuevamente: ");
                pedirValor = 1;
            } else {
                pedirValor = 0;
            }

        }

    } while (pedirValor);

    // Retornamos el valor obtenido
    return valorObtenido;

}


/*
 * Esta función solicita al usuario que ingrese un valor del tipo double.
 * La función no retorna hasta obtener un valor válido.
 */
double leerValorDouble() {

    int pedirValor; // Usaremos esta variable para saber si debemos volver a pedir el valor
    char valueBuffer[MAX_LONG_VALOR];// Buffer del valor ingresado por teclado
    double valorObtenido; // Aqui guardaremos el valor a devolver

    do { // Iteremos para pedir el valor

        // Pedir al usuario un valor y limpiarlo
        fflush(stdin);
        char *buffer = fgets(valueBuffer, MAX_LONG_VALOR, stdin);
        limpiarTexto(buffer);

        // Ahora chequeamos lo que se ingresó
        if(buffer == NULL) {

            pedirValor = 1; // Si la llamada retornó null, no se ingresó nada válido

        } else if (!strcmp(buffer, "0")) {

            valorObtenido = 0;  // El valor ingresado es 0, devolvemos el número 0.
            pedirValor = 0;     // Ya tenemos el valor, no seguir iterando

        } else {

            // Ahora debemos convertir la cadena ingresada a un número.
            valorObtenido = atof(buffer);

            // Si devuelve 0, el valor obtenido es erróneo.
            if(valorObtenido == 0) {
                printf("\tValor incorrecto, ingrese nuevamente: ");
                pedirValor = 1;
            } else {
                pedirValor = 0;
            }

        }

    } while (pedirValor);

    // Retornamos el valor obtenido
    return valorObtenido;

}


/*
 * Esta función verifica que la matriz tenga apuntadores válidos
 */
int verificarMatriz(struct MATRIZ *matriz) {

    // La matriz no existe, devolvemos falso
    if(matriz == NULL) {
        return  0;
    }

    // Recorremos el vector de filas de la matriz
    int i;
    double **elementos = matriz->elementos;

    for(i=0; i<matriz->dimension; i++) {

        // Si el vector es nulo la matriz es inválida
        if(elementos[i] == NULL) {
            return 0;
        }

    }

    return 1;

}


/*
 * Esta función toma un texto como parámetro y elimina todos los caracteres
 * no imprimibles del mismo.
 */
void limpiarTexto(char *texto) {

    // Calculamos la longitud total texto a limpiar
    int longTotal = strlen(texto);

    // Utilizaremos esta cadena como buffer para construir el texto limpio
    char *textoBuffer = (char *) malloc(sizeof(char) * longTotal + 1);

    // Iteramos sobre la cadena
    int i, j; // j sera usada como indice de escritura para el buffer
    j = 0;

    for (i=0; i<longTotal; i++) {

        // Si el caracter no es un caracter de control, lo escribirmos en el buffer
        if(isprint(texto[i])) {
            textoBuffer[j] = texto[i];
            j++;
        }

    }

    // Escribimos el fin de cadena en el buffer
    textoBuffer[j] = 0;

    // Copiamos el buffer en la cadena original
    strcpy(texto, textoBuffer);

    // Vaciamos el buffer
    free(textoBuffer);

}


/*
 * Esta funcion verifica que no exista un archivo con el nombre especificado
 */
int archivoExistente(char *nombreArchivo) {

    // Intentamos abrir un archivo con el nombre especificado
    FILE *filePointer = fopen(nombreArchivo, "r");

    // Si la apertura fue exitosa, entonces el archivo existe
    if(filePointer != NULL) {

        fclose(filePointer);
        return 1;
        
    } else {
        return 0;
    }

}


/*
 * Esta funcion se encarga de guardar todo el contenido de objeto MATRIZ
 * en un archivo con el nombre especificado.
 */
void guardarContenidoMatriz(char *nombreArchivo, struct MATRIZ *matriz) {

    // Creamos y abrimos el archivo
    FILE *filePointer = fopen(nombreArchivo, "w");

    // Escribimos la dimension
    fprintf(filePointer, "dimension = %d\n", matriz->dimension);

    // Ahora escribimos los elementos individuales
    int i,j;
    for(i=0; i<matriz->dimension; i++) {
        for(j=0; j<matriz->dimension; j++) {
            fprintf(filePointer, "matriz[%d][%d] = %lf\n",i , j, matriz->elementos[i][j]);
        }
    }

    // Cerramos el archivo
    fclose(filePointer);

}


/*
 * Esta función genera un timestamp en formato de cadena y lo guarda
 * en el puntero pasado por parámetro.
 */
void generarTimestamp(char *timestamp) {

  struct tm *timeContenedor; // Estructura contenedora de datos de tiempo
  time_t timeValue; // Declaramos una variable de tipo "time"

  // Generamos un timestamp
  timeValue = time(NULL);
  timeContenedor = localtime(&timeValue);

  // Convertimos el timestamp a un valor legible
  sprintf(timestamp, "%04d-%02d-%02d %02d:%02d:%02d",
	  timeContenedor->tm_year+1900, timeContenedor->tm_mon+1,
          timeContenedor->tm_mday, timeContenedor->tm_hour,
          timeContenedor->tm_min, timeContenedor->tm_sec);

}
